package appml

import (
	"fmt"
	"strings"
)

const err_fileprefix = "/appml@"

var err_print_fileprefix = true

// 包含运行时信息的错误
type Err struct {
	file string
	line int
	msg  string
	err  error
}

// 创建一个包含当前文件/当前行号等运行时信息的错误
func NewErr(pc uintptr, file string, line int, ok bool) *Err {
	if err_print_fileprefix {
		return &Err{file: file, line: line}
	}
	if pos := strings.Index(file, err_fileprefix); pos > 0 {
		file = file[pos+len(err_fileprefix):]
		if pos = strings.Index(file, "/"); pos > 0 {
			file = file[pos+1:]
		}
	}
	return &Err{file: file, line: line}
}

// 设置原始错误，如果 err 为 nil 则返回 nil，否则返回 *Err 本身。
// 如果希望只返回 *Err，不管 err 是否为 nil，请使用 Err 方法。
// 注：If 返回的是 error 而不是 *Err。如果返回 *Err 则在 If(nil) 再转换为 error 接口时不会为 nil。
func (e *Err) If(err error) error {
	if err == nil || e == nil {
		return nil
	}
	return e.Err(err)
}

// 设置原始错误，返回 *Err 本身，如果 err 为 nil 则返回 nil （类型为 *Err）。
// 如果希望 err 为 nil 时返回 nil (类型为 error)，请使用 If 方法。
func (e *Err) Err(err error) *Err {
	if err == nil || e == nil {
		return nil
	}
	e.err = err
	return e
}

// 设置错误消息
func (e *Err) Msg(format string, args ...any) *Err {
	if e == nil {
		return nil
	}
	if len(args) > 0 {
		e.msg = fmt.Sprintf(format, args...)
	} else {
		e.msg = format
	}
	return e
}

// 除去包装，返回原始错误
func (e *Err) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.err
}

// 使用 Unwrap() 除去包装，查找最底层的非 nil 的错误。如果 *Err 本身为 nil，则返回 nil。
func (e *Err) LastUnderlying() (last error) {
	if e == nil {
		return nil
	}
	last = e
	ee := error(e)
	for {
		if uw, ok := ee.(interface{ Unwrap() error }); ok {
			ee = uw.Unwrap()
			if ee != nil {
				last = ee
				continue
			}
		}
		break
	}
	return
}

// 实现 error 接口
func (e *Err) Error() string {
	if e == nil {
		return fmt.Sprintf("%T(nil)", e)
	}
	if e.err == nil && e.msg == "" {
		return fmt.Sprintf("Error @ %s:%d", e.file, e.line)
	}
	if e.err == nil {
		return fmt.Sprintf("%s @ %s:%d", e.msg, e.file, e.line)
	}
	if ev, ok := e.err.(*Err); ok {
		if e.msg == "" {
			return fmt.Sprintf("@ %s:%d\n%s", e.file, e.line, e.err.Error())
		}
		return fmt.Sprintf("%s\n    @ %s:%d\n%s", e.msg, e.file, e.line, ev.Error())
	}
	if e.msg == "" {
		return fmt.Sprintf("%v @ %s:%d", e.err, e.file, e.line)
	}
	return fmt.Sprintf("%s %v @ %s:%d", e.msg, e.err, e.file, e.line)
}
